package com.sprucetec.live.aspect;

import com.alibaba.fastjson.JSON;
import com.sprucetec.live.common.webcontext.LoginUserContext;
import com.sprucetec.live.entity.LiveSystemLog;
import com.sprucetec.live.enums.SystemLogEnum;
import com.sprucetec.live.service.LiveSystemLogService;
import com.sprucetec.live.util.DeviceUtils;
import com.sprucetec.live.annotation.SystemControllerLog;
import com.sprucetec.live.annotation.SystemServiceLog;
import com.sprucetec.live.base.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.core.NamedThreadLocal;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * @Auther: wangshaobo
 * @Date: 2018/10/10 10:07
 * @Description:
 */
@Aspect
@Component
@Slf4j
public class SystemAspect {
    private static final ThreadLocal<Date> BEGIN_TIME_THREAD_LOCAL = new NamedThreadLocal<>("ThreadLocal beginTime");
    private static final ThreadLocal<LiveSystemLog> LOG_THREAD_LOCAL = new NamedThreadLocal<>("ThreadLocal liveSystemLog");
    private static final ThreadLocal<User> CURRENT_USER = new NamedThreadLocal<>("ThreadLocal user");

    private static final String CONTROLLER = "controller";
    private static final String SERVICE = "service";

    @Autowired
    HttpServletRequest request;

    @Qualifier("springThreadPoolTaskExecutor")
    @Autowired
    ThreadPoolTaskExecutor springThreadPoolTaskExecutor;
    @Autowired
    LiveSystemLogService logService;

    @Bean(name = "springThreadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
        pool.setKeepAliveSeconds(300);
        //核心线程池数
        pool.setCorePoolSize(1);
        //最大线程
        pool.setMaxPoolSize(1);
        //队列容量
        //pool.setQueueCapacity(1000);

        //队列满，线程被拒绝执行策略
        pool.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
        return pool;
    }

    /**
     * Service层切点
     */
    @Pointcut("@annotation(com.sprucetec.live.annotation.SystemServiceLog)")
    public void serviceAspect() {
    }

    /**
     * Controller层切点 注解拦截
     */
    @Pointcut("@annotation(com.sprucetec.live.annotation.SystemControllerLog)")
    public void controllerAspect() {
    }

    /**
     * 前置通知 用于拦截Controller层记录用户的操作的开始时间    
     *
     * @param joinPoint 切点    
     * @throws InterruptedException     
     */
    @Before("controllerAspect()||serviceAspect()")
    public void doBefore(JoinPoint joinPoint) throws InterruptedException {
        Date beginTime = new Date();
        //线程绑定变量（该数据只有当前请求的线程可见）
        BEGIN_TIME_THREAD_LOCAL.set(beginTime);
        //获取当前登录用户     
        User user = null;
        String annotationType = getAnnotationType(joinPoint);
        if (annotationType.equals(CONTROLLER)) {
            user = LoginUserContext.getUser();
            if (user == null) {
                Object userParam = request.getAttribute(User.USER_KEY);
                if (userParam != null) {
                    user = (User) userParam;
                }

            }
        }
        if (user != null) {
            CURRENT_USER.set(user);
        }
    }


    /**
     * 后置通知 用于拦截Controller层记录用户的操作
     *
     * @param joinPoint 切点    
     */
    @After("controllerAspect()||serviceAspect()")
    public void doAfter(JoinPoint joinPoint) {
        LiveSystemLog liveSystemLog = new LiveSystemLog();
        User user = CURRENT_USER.get();
        String title = "";
        //日志类型(info:入库,error:错误)
        String type = "info";
        //请求的Uri
        String requestUri = "";
        String annotationType = getAnnotationType(joinPoint);
        if (annotationType.equals(CONTROLLER)) {
            requestUri = request.getRequestURI();
            boolean mobileDevice = DeviceUtils.isMobileDevice(request);
            if (mobileDevice) {
                //是mobile
                boolean iosDevice = DeviceUtils.isIOSDevice(request);
                if (iosDevice) {
                    liveSystemLog.setDeviceType("IOS");
                } else {
                    liveSystemLog.setDeviceType("ANDROID");
                }
            } else {
                //pc
                liveSystemLog.setDeviceType("PC");
            }

        } else if (annotationType.equals(SERVICE)) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            requestUri = joinPoint.getTarget().getClass().getName() + "." + method.getName();
        }
        //请求的方法类型(post/get)
        //String method = request.getMethod();
        try {
            title = getMethodDescription(joinPoint);
        } catch (Exception e) {
            e.printStackTrace();
            log.error(" SystemAspect doAfter error:", e);
        }

        //得到线程绑定的局部变量（开始时间）  
        long beginTime = BEGIN_TIME_THREAD_LOCAL.get().getTime();
        //2、结束时间
        long endTime = System.currentTimeMillis();

        //liveSystemLog.setId(UuidUtils.createUUID());
        //liveSystemLog.setId(new ObjectId().toHexString());
        liveSystemLog.setLogTitle(title);
        liveSystemLog.setLogType(type);
        liveSystemLog.setUrl(requestUri);
        //参数
        List<Object> params = Arrays.asList(joinPoint.getArgs());
        String paramStr = "";
        if (!CollectionUtils.isEmpty(params)) {
            paramStr = params.toString();
            if (paramStr.length() > 65535) {
                paramStr = paramStr.substring(0, 65535);
            }
        }
        liveSystemLog.setRequestMessage(paramStr);
        //assetLog.setException("");
        if (user != null) {
            liveSystemLog.setCreateUser(user.getEmail() + "-" + user.getName());
        } else {
            liveSystemLog.setCreateUser("");
        }
        Date operateDate = BEGIN_TIME_THREAD_LOCAL.get();
        liveSystemLog.setCT(operateDate);
        liveSystemLog.setCostTime((int) (endTime - beginTime));

        if (annotationType.equals(CONTROLLER)) {
            liveSystemLog.setTargetType((byte) 0);
        } else if (annotationType.equals(SERVICE)) {
            liveSystemLog.setTargetType((byte) 1);
        }
        // 优化:通过线程池来执行日志保存           
        //springThreadPoolTaskExecutor.execute(new SaveLogThread(liveSystemLog, logService));
        LOG_THREAD_LOCAL.set(liveSystemLog);
        BEGIN_TIME_THREAD_LOCAL.remove();
        CURRENT_USER.remove();
    }

    /**
     * 后置返回通知，记录返回参数
     *
     * @param res
     * @throws Throwable
     */
    @AfterReturning(returning = "res", pointcut = "controllerAspect()||serviceAspect()")
    public void doAfterReturning(Object res) throws Throwable {
        // 处理完请求，返回内容        
        if (null != res) {
            String message = JSON.toJSONString(res);
            LiveSystemLog liveSystemLog = LOG_THREAD_LOCAL.get();
            if (liveSystemLog != null) {
                if (message.length() > 65535) {
                    message = message.substring(0, 65535);
                }
                liveSystemLog.setResultMessage(StringUtils.isBlank(message) ? "" : message);

                //使用线程池 保存日志
                springThreadPoolTaskExecutor.execute(new SaveLogThread(liveSystemLog, logService));
                LOG_THREAD_LOCAL.remove();
            }
        }

    }

    /**
     * 异常通知 记录操作报错日志  
     *
     * @param joinPoint   
     * @param e    
     */
    @AfterThrowing(pointcut = "controllerAspect()||serviceAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        log.info("========== the exception:", e);
        LiveSystemLog liveSystemLog = LOG_THREAD_LOCAL.get();
        if (liveSystemLog != null) {
            liveSystemLog.setLogType("error");
            List<Object> params = Arrays.asList(joinPoint.getArgs());
            String paramStr = "";
            if (!CollectionUtils.isEmpty(params)) {
                paramStr = params.toString();
                if (paramStr.length() > 65535) {
                    paramStr = paramStr.substring(0, 65535);
                }
            }
            liveSystemLog.setRequestMessage(paramStr);
            if (e.toString().length() > 1000) {
                liveSystemLog.setException(e.toString().substring(0, 1000));
            } else {
                liveSystemLog.setException(e.toString());
            }
            //使用线程池 更新日志
            springThreadPoolTaskExecutor.execute(new SaveLogThread(liveSystemLog, logService));
            LOG_THREAD_LOCAL.remove();
        }
    }


    /**
     *   
     * 获取注解中对方法的描述信息 用于service层注解    
     *
     * @param joinPoint 切点  
     * @return discription    
     */
/*    public static String getServiceMthodDescription(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SystemServiceLog serviceLog = method.getAnnotation(SystemServiceLog.class);
        return serviceLog.description();
    }*/

    /**
     * 获取注解中对方法的描述信息
     */
    private static String getMethodDescription(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        Method realMethod = null;
        try {
            realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            log.error("getMethodDescription method is error :", e);
        }
        if (null != realMethod) {
            if (realMethod.isAnnotationPresent(SystemControllerLog.class)) {
                SystemControllerLog systemControllerLog = realMethod.getAnnotation(SystemControllerLog.class);
                return systemControllerLog.description();
            } else if (realMethod.isAnnotationPresent(SystemServiceLog.class)) {
                SystemServiceLog systemServiceLog = realMethod.getAnnotation(SystemServiceLog.class);
                return systemServiceLog.description();
            }
        }

        return null;
    }

    /**
     * 判断注解类型
     */
    private static String getAnnotationType(JoinPoint joinpoint) {
        MethodSignature signature = (MethodSignature) joinpoint.getSignature();
        Method method = signature.getMethod();

        Method realMethod = null;
        try {
            realMethod = joinpoint.getTarget().getClass().getDeclaredMethod(signature.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            log.error("getMethodDescription method is error :", e);
        }
        if (null != realMethod) {
            if (realMethod.isAnnotationPresent(SystemControllerLog.class)) {
                return CONTROLLER;
            } else if (realMethod.isAnnotationPresent(SystemServiceLog.class)) {
                return SERVICE;
            }
        }
        return "";
    }


    /**
     * 保存日志 线程
     */
    private static class SaveLogThread implements Runnable {

        private LiveSystemLog liveSystemLog;
        private LiveSystemLogService logService;

        SaveLogThread(LiveSystemLog liveSystemLog, LiveSystemLogService logService) {
            this.liveSystemLog = liveSystemLog;
            this.logService = logService;
        }

        @Override
        public void run() {
            logService.saveLog(liveSystemLog);
        }
    }

    /**
     * 日志更新 线程
     */
    private static class UpdateLogThread implements Runnable {

        private LiveSystemLog liveSystemLog;
        private LiveSystemLogService logService;

        UpdateLogThread(LiveSystemLog liveSystemLog, LiveSystemLogService logService) {
            this.liveSystemLog = liveSystemLog;
            this.logService = logService;
        }

        @Override
        public void run() {
            //判断是否插入成功
            /*AssetLog aLog = logService.getAssetLogById(assetLog.getId());
            if (aLog == null) {
                try {
                    //防止多线程未插入数据，更新失败,休眠

                    Thread.sleep(100);
                } catch (Exception e) {
                    log.error("sleep is error",e);
                }
            }*/
            logService.updateLog(liveSystemLog);
        }
    }

}
